package com.ag.improvedjava.iterable;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import com.ag.improvedjava.exceptions.OverflowException;
import com.ag.improvedjava.utils.Function;
import com.ag.improvedjava.utils.Function2;
import com.ag.improvedjava.utils.Grouping;
import com.ag.improvedjava.utils.Lookup;
import com.ag.improvedjava.utils.OrderedIterable;
import com.ag.improvedjava.utils.Predicate;

public interface ImprovedIterable<E> extends Iterable<E> {

	
	/***
	 * Returns an {@link ImprovedIterable} containing the elements matching the predicate.
	 * @param predicate
	 * @return
	 */
	public ImprovedIterable<E> where(Predicate<E> predicate);
	
	/***
	 * Applies the transform to every element of the {@link ImprovedIterable} and returns a new {@link ImprovedIterable}.
	 * @param transform
	 * @return
	 */
	public <R> ImprovedIterable<R> select(Function<E, R> transform);
	
	/***
	 * Transforms every element of the input {@link ImprovedIterable<E>} to a {@link Iterable<R>} and flattens the result
	 * into one {@link ImprovedIterable<R>}.   
	 * @param transform
	 * @return
	 */
	public <R> ImprovedIterable<R> selectMany(Function<E, Iterable<R>> transform);
	
	/***
	 * Transforms every element of the input {@link ImprovedIterable<E>} to a {@link Iterable<U>} and flattens the result
	 * into one {@link ImprovedIterable<R>} according to the resultSelector.   
	 * @param transform
	 * @return
	 */
	public <U, R> ImprovedIterable<R> selectMany(Function<E, Iterable<U>> transform, Function2<E, U, R> resultSelector);
	
	/***
	 * Returns the number of elements as an integer contained in the {@link ImprovedIterable}.
	 * Throws {@link OverflowException} if the Iterable contains more than Integer.MAX_VALUE elements.
	 * @return
	 */
	public int count();
	
	/***
	 * Returns the number of elements as an integer contained in the {@link ImprovedIterable} and matching the predicate.
	 * Throws {@link OverflowException} if the Iterable contains more than Integer.MAX_VALUE elements.
	 * @return
	 */
	public int count(Predicate<E> predicate);
	
	/***
	 * Returns the number of elements as a long contained in the {@link ImprovedIterable}.
	 * @return
	 */
	public long longCount();
	
	/***
	 * Returns the number of elements as a long contained in the {@link ImprovedIterable} and matching the predicate.
	 * @return
	 */
	public long longCount(Predicate<E> predicate);
	
	/***
	 * Concatenates the {@link iterable} at the end of this the current instance.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> concat(final Iterable<E> iterable);
	
	/***
	 * Returns a boolean indicating whether there is at least one element in the {@link ImprovedIterable}.
	 * @return
	 */
	public boolean any();
	
	/***
	 * Returns a boolean indicating there is at least one element in the {@link ImprovedIterable} matching the predicate.
	 * @return
	 */
	public boolean any(Predicate<E> predicate);
	
	/***
	 * Returns a boolean indicating whether all the elements in the {@link ImprovedIterable} match the predicate.
	 * @return
	 */
	public boolean all(Predicate<E> predicate);
	
	/***
	 * Returns the first element in the {@link ImprovedIterable}.
	 * Throws {@link NoSuchElementException} if the sequence is empty. 
	 * @return
	 */
	public E first();
	
	/***
	 * Returns the first element in the {@link ImprovedIterable} matching the predicate.
	 * Throws {@link NoSuchElementException} if the sequence is empty or if no match is found. 
	 * @return
	 */
	public E first(Predicate<E> predicate);
	
	/***
	 * Returns the last element in the {@link ImprovedIterable}.
	 * Throws {@link NoSuchElementException} if the sequence is empty. 
	 * @return
	 */
	public E last();
	
	/***
	 * Returns the last element in the {@link ImprovedIterable} matching the predicate.
	 * Throws {@link NoSuchElementException} if the sequence is empty or if no match is found. 
	 * @return
	 */
	public E last(Predicate<E> predicate);
	
	/***
	 * Returns the single element in the {@link ImprovedIterable}.
	 * Throws {@link NoSuchElementException} if the sequence is empty or contains more than one element. 
	 * @return
	 */
	public E single();
	
	/***
	 * Returns the single element in the {@link ImprovedIterable} matching the predicate.
	 * Throws {@link NoSuchElementException} if the sequence is empty or contains more than one element matching the predicate.
	 * @return
	 */
	public E single(Predicate<E> predicate);
	
	/***
	 * Returns an instance of {@link ImprovedIterable} containing the input element if the original sequence is empty, 
	 * otherwise returns the input sequence.
	 * @param element
	 * @return
	 */
	public ImprovedIterable<E> defaultIfEmpty(E element);
	
	/***
	 * Returns an aggregated value resulting from after applying the input function to all the elements in the sequence, 
	 * starting from the first element.
	 * @param aggregator
	 * @return
	 */
	public E aggregate(Function2<E, E, E> aggregator);
	
	/***
	 * Returns an aggregated value resulting from after applying the input function to all the elements in the sequence, 
	 * starting from the input ssed.
	 * @param aggregator
	 * @return
	 */
	public <R> R aggregate(R seed, Function2<R, E, R> aggregator);
	
	/***
	 * Returns a sequence of distinct elements.
	 * @return
	 */
	public ImprovedIterable<E> distinct();

	/***
	 * Returns a sequence of distinct elements, using the input comparer to test equality.
	 * @return
	 */
	public ImprovedIterable<E> distinct(Comparator<E> comparer);
	
	/***
	 * Returns the union of distinct elements between {@link this} and the input iterable.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> union(final Iterable<E> iterable);
	
	/***
	 * Returns the union of distinct elements between {@link this} and the input iterable, using the 
	 * input comparer to test equality.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> union(final Iterable<E> iterable, Comparator<E> comparer);

	/***
	 * Returns a sequence of distinct elements which are present in both {@link this} and the input sequence.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> intersect(final Iterable<E> iterable);
	
	/***
	 * Returns a sequence of distinct elements which are present in both {@link this} and the input sequence, using
	 * the input comparer to test equality.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> intersect(final Iterable<E> iterable, Comparator<E> comparer);
	
	/***
	 * Returns a sequence of distinct elements of {@link this} sequence which are not present in the input sequence.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> except(final Iterable<E> iterable);

	/***
	 * Returns a sequence of distinct elements of {@link this} sequence which are not present in the input sequence,
	 * using the input comparer to test equality.
	 * @param iterable
	 * @return
	 */
	public ImprovedIterable<E> except(final Iterable<E> iterable, Comparator<E> comparer);
	
	/***
	 * Returns a {@link Lookup<K, E>} from this iterable according to a keySelector function.
	 * @param keySelector
	 * @return
	 */
	public <K> Lookup<K, E> toLookup(Function<E, K> keySelector);
	
	/***
	 * Returns a {@link Lookup<K, E>} from this iterable according to a keySelector function, using the input comparer
	 * to test key equality.
	 * @param keySelector
	 * @return
	 */
	public <K> Lookup<K, E> toLookup(Function<E, K> keySelector, Comparator<K> comparer);
	
	/***
	 * Returns a {@link Lookup<K, E>} from this iterable according to a keySelector and elementSelector functions.
	 * @param keySelector
	 * @return
	 */
	public <K, R> Lookup<K, R> toLookup(Function<E, K> keySelector, Function<E, R> elementSelector);

	/***
	 * Returns a {@link Lookup<K, E>} from this iterable according to a keySelector and elementSelector functions, using 
	 * the input comparer to test key equality.
	 * @param keySelector
	 * @return
	 */
	public <K, R> Lookup<K, R> toLookup(Function<E, K> keySelector, Function<E, R> elementSelector, Comparator<K> comparer);

	/**
	 * Returns the resulting join between this iterable and the input inner iterable based on matching keys.
	 * @param inner
	 * @param outerKeySelector
	 * @param innerKeySelector
	 * @param resultSelector
	 * @return
	 */
	public <U, K, R> ImprovedIterable<R> join(final Iterable<U> inner,
			Function<E, K> outerKeySelector, Function<U, K> innerKeySelector,
			Function2<E, U, R> resultSelector);

	/***
	 * Returns the resulting join between this iterable and the input inner iterable based on matching keys, using the
	 * input comparer to test key equality.
	 * @param inner
	 * @param outerKeySelector
	 * @param innerKeySelector
	 * @param resultSelector
	 * @param comparer
	 * @return
	 */
	public <K, U, R> ImprovedIterable<R> join(final Iterable<U> inner,
			Function<E, K> outerKeySelector, Function<U, K> innerKeySelector,
			Function2<E, U, R> resultSelector, Comparator<K> comparer);

	/***
	 * Groups the elements of this iterable according to the input keySelector.
	 * @param keySelector
	 * @return
	 */
	public <K> ImprovedIterable<Grouping<K, E>> groupBy(Function<E, K> keySelector);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector, using the input comparer for 
	 * testing key equality.
	 * @param keySelector
	 * @param comparer
	 * @return
	 */
	public <K> ImprovedIterable<Grouping<K, E>> groupBy(Function<E, K> keySelector, Comparator<K> comparer);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector and elementSelector.
	 * @param keySelector
	 * @param elementSelector
	 * @return
	 */
	public <K, U> ImprovedIterable<Grouping<K, U>> groupBy(Function<E, K> keySelector, Function<E, U> elementSelector);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector and elementSelector, 
	 * using the input comparer for testing key equality.
	 * @param keySelector
	 * @param elementSelector
	 * @return
	 */
	public <K, U> ImprovedIterable<Grouping<K, U>> groupBy(
			Function<E, K> keySelector, Function<E, U> elementSelector,
			Comparator<K> comparer);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector and flattens the result into 
	 * one {@link ImprovedIterable<R>} according to the resultSelector. 
	 * @param keySelector
	 * @param resultSelector
	 * @return
	 */
	public <K, R> ImprovedIterable<R> groupBy(Function<E, K> keySelector, Function2<K, Iterable<E>, R> resultSelector);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector and flattens the result into 
	 * one {@link ImprovedIterable<R>} according to the resultSelector, using the input comparer for testing key equality.
	 * @param keySelector
	 * @param resultSelector
	 * @param comparer
	 * @return
	 */
	public <K, R> ImprovedIterable<R> groupBy(Function<E, K> keySelector, Function2<K, Iterable<E>, R> resultSelector,
			Comparator<K> comparer);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector and elementSelector and 
	 * flattens the result into one {@link ImprovedIterable<R>} according to the resultSelector. 
	 * @param keySelector
	 * @param elementSelector
	 * @param resultSelector
	 * @return
	 */
	public <K, U, R> ImprovedIterable<R> groupBy(Function<E, K> keySelector, Function<E, U> elementSelector, 
			Function2<K, Iterable<U>, R> resultSelector);
	
	/***
	 * Groups the elements of this iterable according to the input keySelector and elementSelector and flattens 
	 * the result into one {@link ImprovedIterable<R>} according to the resultSelector, 
	 * using the input comparer for testing key equality.
	 * @param keySelector
	 * @param elementSelector
	 * @param resultSelector
	 * @param comparer
	 * @return
	 */
	public <K, U, R> ImprovedIterable<R> groupBy(Function<E, K> keySelector, Function<E, U> elementSelector, 
			Function2<K, Iterable<U>, R> resultSelector, Comparator<K> comparer);

	
	/***
	 * Correlates the elements of two sequences based on key equality and groups the results.
	 * @param inner
	 * @param outerKeySelector
	 * @param innerKeySelector
	 * @param resultSelector
	 * @return
	 */
	public <K, U, R> ImprovedIterable<R> groupJoin(final Iterable<U> inner,
			Function<E, K> outerKeySelector, Function<U, K> innerKeySelector,
			Function2<E, Iterable<U>, R> resultSelector); 

	/***
	 * Correlates the elements of two sequences based on key equality and groups the results, using the comparer
	 * for key equality.
	 * @param inner
	 * @param outerKeySelector
	 * @param innerKeySelector
	 * @param resultSelector
	 * @param comparer
	 * @return
	 */
	public <K, U, R> ImprovedIterable<R> groupJoin(final Iterable<U> inner,
			Function<E, K> outerKeySelector, Function<U, K> innerKeySelector,
			Function2<E, Iterable<U>, R> resultSelector, Comparator<K> comparer);
	
	
	/***
	 * Returns a specified number of contiguous elements from the start of a sequence.
	 * @param count
	 * @return
	 */
	public ImprovedIterable<E> take(int count);
	
	/***
	 * Returns elements from a sequence as long as a specified condition is true.
	 * @param predicate
	 * @return
	 */
	public ImprovedIterable<E> takeWhile(Predicate<E> predicate);
	
	/***
	 * Bypasses a specified number of elements in a sequence and then returns the remaining elements.
	 * @param count
	 * @return
	 */
	public ImprovedIterable<E> skip(int count);
	
	/***
	 * Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements.
	 * @param predicate
	 * @return
	 */
	public ImprovedIterable<E> skipWhile(Predicate<E> predicate);
	
	/***
	 * Executes an action on every element of the {@link ImprovedIterable}.
	 * @param action the action to execute
	 */
//	public void forEach(Action<E> action);
	
	/**
	 * Converts an {@link ImprovedIterable} instance to a list.
	 * @return
	 */
	public List<E> toList();
	
	/**
	 * Converts an {@link ImprovedIterable} instance to an array of {@link Object}.
	 * @return
	 */
	public Object[] toArray();
	
	
	/***
	 * Converts an {@link ImprovedIterable} instance to a dictionary according to a specified keySelector function.
	 * @param keySelector
	 * @return
	 */
	public <K> Map<K, E> toDictionary(Function<E, K> keySelector);
	
	/***
	 * Converts an {@link ImprovedIterable} instance to a dictionary according to specified keySelector and 
	 * elementSelector function.
	 * @param keySelector
	 * @param elementSelector
	 * @return
	 */
	public <K, U> Map<K, U> toDictionary(Function<E, K> keySelector, Function<E, U> elementSelector);
	
	/***
	 * Converts an {@link ImprovedIterable} instance to a dictionary according to a specified keySelector function,
	 * using the input comparer to test key equality.
	 * @param keySelector
	 * @param comparer
	 * @return
	 */
	public <K> Map<K, E> toDictionary(Function<E, K> keySelector, Comparator<K> comparer);
	
	/***
	 * Converts an {@link ImprovedIterable} instance to a dictionary according to specified keySelector and 
	 * elementSelector function, using the input comparer to test key equality.
	 * @param keySelector
	 * @param elementSelector
	 * @param comparer
	 * @return
	 */
	public <K, U> Map<K, U> toDictionary(Function<E, K> keySelector, Function<E, U> elementSelector, Comparator<K> comparer);

	/***
	 * Sorts the elements of a sequence in ascending order according to a key.
	 * @param keySelector
	 * @return
	 */
	public <K extends Comparable<? super K>> OrderedIterable<E> orderBy(Function<E, K> keySelector); 

	/***
	 * Sorts the elements of a sequence in ascending order according to a key, using the input comparer to
	 * test key equality.
	 * @param keySelector
	 * @param comparer
	 * @return
	 */
	public <K> OrderedIterable<E> orderBy(Function<E, K> keySelector, Comparator<K> comparer);

	/***
	 * Sorts the elements of a sequence in descending order according to a key.
	 * @param keySelector
	 * @return
	 */
	public <K extends Comparable<? super K>> OrderedIterable<E> orderByDescending(Function<E, K> keySelector); 
	
	/***
	 * Sorts the elements of a sequence in descending order according to a key, using the input comparer to
	 * test key equality.
	 * @param keySelector
	 * @param comparer
	 * @return
	 */
	public <K> OrderedIterable<E> orderByDescending(Function<E, K> keySelector, Comparator<K> comparer);
	
	/***
	 * Inverts the order of the elements in this sequence.
	 * @return
	 */
	public ImprovedIterable<E> reverse();
	
	/***
	 * Returns the element at the specified input index.
	 * Throws a {@link IndexOutOfBoundsException} if the index falls outside the bounds.
	 * @param index
	 * @return
	 */
	public E elementAt(int index);

	/***
	 * Determines whether the sequence contains the specified input value, using the default equals method
	 * to test equality.
	 * @param value
	 * @return
	 */
	public boolean contains(E value);
	
	/***
	 * Determines whether the sequence contains the specified input value, using the comparer to test equality. 
	 * The default equality comparer is used if the input comparer is null.
	 * @param value
	 * @param comparer
	 * @return
	 */
	public boolean contains(E value, Comparator<E> comparer);
	
	/***
	 * Casts the elements of this sequence to the type R. 
	 * @throws ClassCastException if an element cannot be cast to type R.
	 * @return
	 */
	public <R> ImprovedIterable<R> cast(Class<R> c);
	
	/***
	 * Filters the elements of this sequence based on a specific type.
	 * @return
	 */
	public <R> ImprovedIterable<R> ofType(Class<R> c);
	
	/***
	 * Compares this sequence to the input iterable, using the default equals method to test equality.
	 * @param iterable
	 * @return
	 */
	public boolean sequenceEqual(Iterable<E> iterable);
	
	/***
	 * Compares this sequence to this input iterable, using the comparer to test equality.
	 * The default equality comparer is used if the input comparer is null.
	 * @param iterable
	 * @param comparer
	 * @return
	 */
	public boolean sequenceEqual(Iterable<E> iterable, Comparator<E> comparer);
	
	/***
	 * Merges two sequences by using the given result selector.
	 * @param iterable
	 * @param resultSelector
	 * @return
	 */
	public <U, R> ImprovedIterable<R> zip(Iterable<U> iterable, Function2<E, U, R> resultSelector);
}